/************************************************************************
Copyright (c) 2010, 2013, C.R. All rights reserved. 

E-mail: crtoyou@gmail.com; crtoyou@163.com

You can redistribute it and/or modify it under the terms of the
GNU Lesser General Public License as published by the
Free Software Foundation version 3 of the License.

This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. if not, write to the Free Software Foundation, Inc., 
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
************************************************************************/

#ifndef __CR_BIT_IO_STREAM_H__
#define __CR_BIT_IO_STREAM_H__

#include <crbsplus.h>
#include <crstream.h>
#include <iostream>

namespace cr {

class bitstream 
{
private:
	bitstream(const bitstream&);
	bitstream& operator = (const bitstream&);

public:
	enum errcode { eos }; // end of stream

	bitstream(stream_base& strm, char obi = 0)
		: m_strm(strm)
		, m_space(0)
	{
		bind(strm.tell(), obi, strm.remain() * 8 - obi);
		DEBUGING(m_final = false);
	}

	~bitstream()
	{
		//call final before bitstream destruct
		ASSERT(m_pos.oBy == m_strm.tell());
		ASSERT(m_final);
	}

	inline void bind(byte* oBy, char obi, size_t len)
	{
		bind(bit_addr(oBy, obi), len);
	}

	inline void bind(bit_addr org, size_t len)
	{
		m_org = m_pos = org;
		m_space = len;
	}

	inline char final()
	{
		ASSERT(!m_final);
		DEBUGING(m_final = true);
		return m_pos.obi;
	}

	void seek(uint16 offset)
	{
		ASSERT(isvalid());
		if (!m_space && !flush())
			throw exception<bitstream>(eos, this);
		if (m_space < offset) {
			seek(m_space);
			seek(offset - m_space);
		}
		else if (offset > 0) {
			m_pos += offset;
			m_space -= offset;
		}
	}

public:
	inline bit_addr tell() const { return m_pos; }
	inline size_t remain() const { return m_space; }
	inline bool isvalid() const { return m_org <= m_pos && m_space >= 0; }

#ifdef _DEBUG
public:
	void dump(std::ostream& out) const 
	{
		if (m_pos.oBy < m_org.oBy)
			return;
		if (m_pos.oBy == m_org.oBy && m_pos.obi <= m_org.obi)
			return;
		bit_size bsize(m_pos.oBy-1-m_org.oBy, m_pos.obi+8-m_org.obi);
		if (m_pos.oBy > m_org.oBy+1) 
			for (int i=0; i<8; ++i)
				dump(out, bsize.oBy);
		dump(out, bsize.obi);
	}
private:
	void dump(std::ostream& out, uint16 blen) const 
	{
		bool vals[1024];
		if (blen <= 1024) {
			if (blen > 0)
				::bitread(vals, m_org.oBy, blen, m_org.obi);
			for (int i=0; i<blen; ++i)
				out << (vals[i]?'1':'0');
			return;
		}
		dump(out, 1024);
		dump(out, blen - 1024);
	}
private:
	bool m_final;
#endif 

public:
	virtual bool flush()
	{
		ASSERT(isvalid());
		//seek calls flush if beyond end
		long offset = m_pos.oBy - m_org.oBy;
		if (offset > 0)
			m_strm.seek(offset);
		bind(m_strm.tell(), m_pos.obi, m_strm.remain() * 8 - m_pos.obi);
		return true;
	}

protected:
	stream_base& m_strm;

protected:
	bit_addr m_org;
	bit_addr m_pos;
	size_t m_space;

};

class obstream : public bitstream
{
public:
	obstream(ostream_base& os, char obi = 0)
		: bitstream(os, obi)
	{}

	inline char final()
	{
		if (!flush())
			throw exception<bitstream>(eos, this);
		return bitstream::final();
	}

	template <size_t SIZ>
	inline void write(const bitstring<SIZ>& bs)
	{
		write(bs.begin(), bs.length());
	}

	void write(const_bit_addr bits, uint16 len)
	{
		ASSERT(isvalid());
		if (!m_space && !flush())
			throw exception<bitstream>(eos, this);
		ASSERT(m_space > 0);
		if (m_space < len) {
			write(bits, m_space);
			write(bits, len - m_space);
		}
		else if (len > 0) {
			::bitcopy(m_pos.oBy, bits.oBy, len, m_pos.obi, bits.obi);
			m_pos += len;
			ASSERT(len <= m_space);
			m_space -= len;
		}
	}

};

class ibstream : public bitstream
{
public:
	ibstream(istream_base& is, char obi = 0)
		: bitstream(is, obi)
	{}

	inline char final()
	{
		if (!flush())
			throw exception<bitstream>(eos, this);
		return bitstream::final();
	}

	template <size_t SIZ>
	inline void read(bitstring<SIZ>& bs, uint16 len)
	{
		MSK_DEF(buf, SIZ);
		bit_addr pos((byte*)buf, 0);
		read(pos, len);
		bs.setbits(pos, len);
	}

	void read(bit_addr bits, uint16 len)
	{
		ASSERT(isvalid());
		if (!m_space && !flush())
			throw exception<bitstream>(eos, this);
		ASSERT(m_space > 0);
		if (m_space < len) {
			read(bits, m_space);
			read(bits += m_space, len - m_space);
		}
		else if (len > 0) {
			::bitcopy(bits.oBy, m_pos.oBy, len, bits.obi, m_pos.obi);
			m_pos += len;
			ASSERT(len <= m_space);
			m_space -= len;
		}
	}

	template <class type_handler>
	inline void scan(size_t bitlen, type_handler* handler)
	{
		if (!m_space && !flush())
			throw exception<bitstream>(eos, this);
		if (m_space < bitlen) {
			scan<type_handler>(m_space, handler);
			scan<type_handler>(bitlen - m_space, handler);
		}
		else if (bitlen > 0) {
			ASSERT(bitlen > 0);
			ASSERT(handler);
			bit_addr endpos = m_pos;
			endpos += bitlen;
			scan<type_handler>(endpos, handler);
			m_pos += bitlen;
			m_space -= bitlen;
		}
	}

	template <class type_handler>
	void scan(bit_addr endpos, type_handler* handler)
	{
		ASSERT(isvalid());
		ASSERT(m_pos < endpos);
		if (m_pos.oBy < endpos.oBy)
		{
			byte by = *m_pos.oBy;
			by >>= m_pos.obi;
			for (int i=m_pos.obi; i<8; ++i)
			{
				handler->step(by & 1);
				by >>= 1;
			}
			m_pos.oBy += 1;
			m_pos.obi = 0;
			while (m_pos.oBy < endpos.oBy)
			{
				byte by = *m_pos.oBy;
				//no need(by >>= 0;)
				for (int i=0; i<8; ++i)
				{
					handler->step(by & 1);
					by >>= 1;
				}
				m_pos.oBy += 1;
				ASSERT(!m_pos.obi);
			}
		}
		ASSERT(m_pos.oBy == endpos.oBy && m_pos.obi <= endpos.obi);
		{
			byte by = *m_pos.oBy;
			by >>= m_pos.obi;
			for (int i=m_pos.obi; i<endpos.obi; ++i)
			{
				handler->step(by & 1);
				by >>= 1;
			}
			m_pos.obi = endpos.obi;
		}
	}

};

}//namespace

#endif//__CR_BIT_IO_STREAM_H__